스프링 코어
1. 개요
1. 개요
스프링 코어는 스프링 프레임워크의 핵심 모듈로서, 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크의 기반을 제공한다. 이 모듈은 엔터프라이즈급 자바 애플리케이션을 구축하는 데 필요한 가장 근본적인 기능들을 담당하며, 제어의 역전과 의존성 주입이라는 핵심 원칙을 구현한다. 스프링 코어의 등장은 복잡한 엔터프라이즈 자바빈즈 개발 방식을 대체하며 자바 엔터프라이즈 개발의 패러다임을 전환시켰다.
스프링 코어는 로드 존슨에 의해 개발되어 2002년 10월에 최초로 공개되었다. 이 프레임워크는 아파치 라이선스 2.0 하에 배포되어 자유롭게 사용, 수정, 배포할 수 있다. 주요 목적은 애플리케이션을 구성하는 객체, 즉 빈의 생성, 구성, 상호 연결을 관리하는 컨테이너를 제공하는 것이다. 이를 통해 개발자는 비즈니스 로직에 더 집중할 수 있고, 결합도가 낮으며 유연성이 높은 애플리케이션을 설계할 수 있다.
이 모듈은 애플리케이션 컨텍스트라는 고급 형태의 컨테이너를 통해 빈의 생명주기를 관리한다. 설정은 전통적인 XML 방식, 어노테이션 기반 방식, 그리고 순수 자바 코드를 사용하는 방식 등 다양한 방법으로 할 수 있다. 또한 스프링 표현 언어, 리소스 추상화, 이벤트 처리, AOP 기초 기능 등 애플리케이션 개발에 필요한 보조 기능들을 포함하고 있다.
스프링 코어는 모듈화된 스프링 프레임워크의 토대가 되며, 스프링 부트, 스프링 시큐리티, 스프링 데이터 같은 다른 모든 프로젝트들이 의존하는 기본 인프라스트럭처를 형성한다. 따라서 스프링 생태계를 이해하고 효과적으로 활용하기 위해서는 스프링 코어의 원리와 기능에 대한 이해가 필수적이다.
2. 핵심 개념
2. 핵심 개념
2.1. 제어의 역전 (IoC)
2.1. 제어의 역전 (IoC)
제어의 역전은 스프링 프레임워크의 핵심 원리 중 하나로, 객체의 생성과 의존 관계 설정, 생명주기 관리에 대한 제어권이 개발자로부터 프레임워크로 넘어가는 것을 의미한다. 전통적인 자바 애플리케이션에서는 객체가 필요로 하는 의존성을 직접 생성하거나 조회하는 방식으로 제어 흐름이 구성된다. 반면 제어의 역전이 적용되면, 객체는 자신이 어떻게 생성되고 연결될지 알지 못하며, 모든 제어 권한을 외부의 컨테이너에 위임한다.
이러한 패턴의 구현체가 바로 스프링 IoC 컨테이너이다. 컨테이너는 설정 정보를 바탕으로 애플리케이션을 구성하는 객체, 즉 빈을 생성하고 관리한다. 개발자는 객체 간의 의존 관계를 XML, 어노테이션, 또는 자바 설정 클래스를 통해 선언적으로 정의하기만 하면, 컨테이너가 런타임에 이를 해석하여 객체를 연결해준다. 이로 인해 비즈니스 로직에 집중할 수 있고, 객체 간의 결합도를 낮춰 유연하고 테스트하기 쉬운 코드를 작성할 수 있다.
제어의 역전의 가장 대표적인 구현 방식이 의존성 주입이다. 의존성 주입은 객체가 필요로 하는 다른 객체를 외부에서 주입받는 방식을 말하며, 생성자 주입, 세터 주입, 필드 주입 등의 방법으로 수행된다. 이를 통해 객체는 구체적인 구현 클래스에 의존하지 않고 인터페이스에만 의존하게 되어, 느슨한 결합을 달성하고 변경에 유연한 시스템을 구축할 수 있다.
결국 제어의 역전은 스프링 코어의 기반이 되는 철학으로, 복잡한 엔터프라이즈 애플리케이션의 개발과 유지보수를 단순화하는 데 기여한다. 이 원칙은 애플리케이션 컨텍스트를 통해 실현되며, 로드 존슨이 저술한 책에서 그 개념이 소개된 이후 현대 소프트웨어 공학의 중요한 디자인 패턴으로 자리 잡았다.
2.2. 의존성 주입 (DI)
2.2. 의존성 주입 (DI)
의존성 주입(Dependency Injection, DI)은 스프링 코어의 핵심 원칙인 제어의 역전(IoC)을 구현하는 구체적인 디자인 패턴이다. 이 패턴은 객체가 자신이 필요로 하는 의존성(다른 객체)을 직접 생성하거나 찾지 않고, 외부 컨테이너로부터 주입받도록 설계하는 방식을 말한다. 이를 통해 객체 간의 결합도가 낮아지고, 코드의 유연성, 재사용성, 테스트 용이성이 크게 향상된다.
의존성 주입은 주로 세 가지 방식으로 구현된다. 생성자 주입은 의존 객체를 빈 생성 시점에 필수적으로 주입하는 방식으로, 불변성을 보장하고 순환 의존성을 방지하는 데 유리하다. 세터 주입은 빈 생성 후 선택적으로 의존성을 설정할 수 있는 방식이며, 필드 주입은 리플렉션을 이용해 직접 필드에 의존성을 주입하는 방식이다. 스프링 프레임워크는 이 세 가지 패턴을 모두 지원하지만, 명시적이고 불변성을 유지하는 생성자 주입을 권장하는 추세이다.
의존성 주입의 핵심 이점은 단위 테스트의 용이성에 있다. 테스트 시 실제 데이터베이스 연결이나 외부 API 호출과 같은 복잡한 의존 객체 대신, 가짜 객체(목 객체 또는 스텁)를 쉽게 주입하여 특정 로직만을 격리하여 테스트할 수 있다. 이는 소프트웨어 품질과 유지보수성을 높이는 데 기여한다.
스프링 컨테이너는 애플리케이션 컨텍스트를 통해 설정 정보(XML, 어노테이션, 자바 설정 클래스)를 읽고, 빈들 간의 의존 관계를 분석하여 필요한 객체를 자동으로 연결해준다. 이 과정에서 개발자는 복잡한 객체 생성과 관리를 프레임워크에 위임함으로써 비즈니스 로직 구현에 더 집중할 수 있게 된다.
2.3. 빈 (Bean)
2.3. 빈 (Bean)
스프링 프레임워크에서 빈은 애플리케이션의 핵심을 이루는 객체를 의미한다. 스프링 컨테이너에 의해 생성되고, 조립되며, 관리되는 모든 객체가 빈이다. 일반적인 자바 객체와 달리, 빈은 스프링 IoC 컨테이너의 생명주기 관리 하에 존재하며, 의존성 주입을 통해 다른 빈들과 관계를 맺고 구성된다.
빈의 정의와 구성 정보는 설정 메타데이터를 통해 제공된다. 이 메타데이터는 전통적으로 XML 파일을 통해 명시되었으나, 최근에는 자바 애너테이션이나 자바 설정 클래스를 이용하는 방식이 더 널리 사용된다. 설정 정보에는 빈의 클래스, 의존 관계, 스코프, 초기화 및 소멸 메서드 등이 포함된다.
스프링 컨테이너는 이 메타데이터를 읽어 빈 정의를 생성하고, 실제 객체를 인스턴스화하며, 의존성을 주입하고, 생명주기 콜백을 처리한다. 가장 기본적이고 기본값인 빈 스코프는 싱글톤으로, 컨테이너당 하나의 인스턴스만 생성되어 재사용된다. 이 외에도 요청마다 새로운 인스턴스를 생성하는 프로토타입 스코프 등 다양한 스코프를 지원한다.
빈은 스프링 애플리케이션의 기본 구성 요소로서, 비즈니스 로직을 구현하는 서비스, 데이터 접근을 담당하는 리포지토리, 웹 요청을 처리하는 컨트롤러 등 다양한 역할을 수행한다. 스프링의 핵심 원리인 제어의 역전과 의존성 주입은 빈을 통해 실현되며, 이는 느슨한 결합도와 높은 테스트 용이성을 가능하게 하는 기반이 된다.
2.4. 애플리케이션 컨텍스트 (ApplicationContext)
2.4. 애플리케이션 컨텍스트 (ApplicationContext)
애플리케이션 컨텍스트는 스프링 프레임워크의 핵심 컨테이너로서, 빈의 정의를 로드하고, 이를 관리하며, 의존성 주입을 통해 객체들을 연결하는 역할을 한다. 이는 단순한 빈 팩토리의 기능을 넘어 메시지 소싱, 이벤트 발행, 리소스 접근과 같은 엔터프라이즈 애플리케이션에 필요한 다양한 기능을 제공한다.
주요 구현체로는 클래스패스 상의 XML 설정 파일을 읽는 ClassPathXmlApplicationContext와 자바 어노테이션 기반 설정을 사용하는 AnnotationConfigApplicationContext 등이 있다. 개발자는 이러한 컨텍스트를 생성하고 초기화함으로써 스프링이 관리하는 모든 빈의 생명주기를 시작하게 된다.
애플리케이션 컨텍스트는 싱글톤 스코프로 등록된 빈을 기본적으로 하나의 인스턴스만 생성하여 관리하며, 필요 시점에 의존성을 주입한다. 이는 제어의 역전 원칙을 구현하는 구체적인 매커니즘으로, 애플리케이션의 설정 정보와 실제 실행 로직을 분리하는 데 기여한다.
컨텍스트는 계층 구조를 가질 수 있어, 부모 컨텍스트에서 정의된 빈을 자식 컨텍스트에서 참조할 수 있도록 한다. 이는 대규모 애플리케이션에서 모듈별로 설정을 분리하거나, 웹 애플리케이션에서 루트와 서블릿 컨텍스트를 구분하는 데 유용하게 활용된다.
3. 기본 구성
3. 기본 구성
3.1. XML 기반 설정
3.1. XML 기반 설정
XML 기반 설정은 스프링 프레임워크의 초기부터 사용된 전통적인 구성 방식이다. 애플리케이션의 구조와 빈들 간의 의존 관계를 XML 파일에 선언적으로 정의하는 방식으로, 설정 정보와 자바 코드를 분리하는 장점을 가진다. 주로 applicationContext.xml 또는 beans.xml과 같은 이름의 파일에 <beans> 루트 요소 안에 각 빈을 <bean> 태그로 정의한다. 각 빈 정의에는 고유한 id나 name, 그리고 해당 객체의 완전한 클래스 이름인 class 속성이 필수적으로 명시된다.
의존성 주입은 XML 설정에서 핵심적으로 구성되는 부분이다. 생성자 주입은 <constructor-arg> 요소를 사용하여 구현하며, ref 속성으로 다른 빈을 참조하거나 value 속성으로 기본형이나 문자열 값을 전달할 수 있다. 세터 주입은 <property> 요소를 사용하며, 마찬가지로 ref나 value 속성을 통해 의존 객체나 값을 설정한다. 이를 통해 제어의 역전 컨테이너는 설정 파일을 읽고, 정의된 빈들을 생성하며, 필요한 의존 관계를 연결한다.
XML 설정은 복잡한 프로퍼티 설정이나 컬렉션 타입 주입에도 유용하다. <list>, <set>, <map>, <props> 등의 전용 요소를 사용하여 빈의 프로퍼티에 리스트나 맵과 같은 복합 객체를 주입할 수 있다. 또한, p: 네임스페이스나 c: 네임스페이스를 활용하면 생성자 주입과 프로퍼티 주입을 더 간결한 속성 형식으로 표현할 수 있어 가독성을 높일 수 있다.
이 방식은 설정이 외부 파일에 명시되어 있어 애플리케이션 재컴파일 없이 구성을 변경할 수 있다는 장점이 있지만, 대규모 프로젝트에서는 파일이 비대해지고 관리가 어려워질 수 있다. 이 한계를 보완하기 위해 스프링 프레임워크는 이후 어노테이션 기반 설정과 자바 기반 설정을 더 적극적으로 지원하게 되었다.
3.2. 어노테이션 기반 설정
3.2. 어노테이션 기반 설정
어노테이션 기반 설정은 XML 파일 대신 자바 소스 코드에 직접 어노테이션을 추가하여 스프링 프레임워크의 빈 정의와 의존 관계를 구성하는 방식이다. 이 방식은 자바 5부터 도입된 어노테이션 기능을 활용하여 설정 정보를 코드와 가깝게 배치함으로써 개발 편의성과 가독성을 높인다. 주요 설정 파일인 XML의 양을 크게 줄일 수 있으며, 컴파일 타임에 일부 오류를 검출할 수 있는 장점이 있다.
이 방식을 사용하려면 스프링 컨테이너에게 어노테이션을 스캔하도록 지시해야 한다. XML 설정에서는 <context:component-scan base-package="..."/> 요소를 사용하고, 자바 기반 설정에서는 @ComponentScan 어노테이션을 사용하여 빈이 위치할 자바 패키지를 지정한다. 이렇게 하면 컨테이너가 지정된 패키지와 그 하위 패키지를 탐색하여 특정 어노테이션이 부여된 클래스를 자동으로 빈으로 등록한다.
핵심적인 어노테이션으로는 빈을 등록하는 @Component와 그 특화된 형태인 @Service, @Repository, @Controller가 있다. 의존성 주입을 위해 @Autowired 어노테이션을 생성자, 세터 메서드, 필드에 적용할 수 있으며, @Qualifier를 함께 사용하여 특정 빈을 명시적으로 지정할 수 있다. 빈의 스코프는 @Scope 어노테이션으로, 초기화 및 소멸 메서드는 @PostConstruct 및 @PreDestroy 어노테이션으로 각각 정의한다.
어노테이션 기반 설정은 XML의 장황함을 해소하고 자바 코드 내에서 일관된 설정을 가능하게 하지만, 설정 변경 시 재컴파일이 필요하다는 점과 XML에 비해 중앙 집중식 관리가 어려울 수 있다는 단점도 있다. 따라서 프로젝트의 규모와 요구사항에 따라 XML 기반 설정, 자바 기반 설정과 함께 적절히 혼용하여 사용하는 것이 일반적이다.
3.3. 자바 기반 설정 (Java Config)
3.3. 자바 기반 설정 (Java Config)
자바 기반 설정은 XML이나 어노테이션 대신 순수 자바 코드를 사용하여 스프링 프레임워크의 빈과 그 의존 관계를 정의하는 방법이다. 이 방식은 @Configuration 어노테이션이 붙은 클래스와 그 안에 @Bean 어노테이션이 붙은 메서드를 통해 애플리케이션 컨텍스트를 구성한다. 자바 코드로 작성되기 때문에 리팩토링과 타입 안전성의 이점을 가지며, 컴파일 타임에 오류를 검출할 수 있다는 장점이 있다.
주요 구성 요소로는 설정 클래스를 표시하는 @Configuration, 빈 정의 메서드를 표시하는 @Bean, 그리고 컴포넌트 스캔을 활성화하는 @ComponentScan 어노테이션이 있다. @Bean 메서드는 빈의 생성을 담당하며, 메서드 내에서 필요한 객체를 생성하고 의존성을 주입한 후 반환하는 로직을 작성할 수 있다. 이때 다른 @Bean 메서드를 호출함으로써 빈 간의 의존성을 자연스럽게 표현한다.
자바 기반 설정은 특히 복잡한 빈 초기화 로직이나 조건부 빈 등록이 필요한 고급 시나리오에서 강력한 표현력을 발휘한다. 또한 XML 설정과 어노테이션 기반 설정을 혼합하여 사용할 수도 있어 기존 프로젝트를 점진적으로 마이그레이션하는 데 유용하다. 스프링 부트는 내부적으로 이 자바 기반 설정 방식을 광범위하게 채택하고 있어, 현대적인 스프링 애플리케이션 개발의 표준 방식으로 자리 잡았다.
4. 빈 스코프와 생명주기
4. 빈 스코프와 생명주기
4.1. 싱글톤과 프로토타입 스코프
4.1. 싱글톤과 프로토타입 스코프
스프링 컨테이너가 관리하는 빈의 기본 스코프는 싱글톤이다. 싱글톤 스코프는 애플리케이션 컨텍스트 내에서 해당 빈의 정의에 대해 정확히 하나의 객체 인스턴스만 생성됨을 의미한다. 이 단일 인스턴스는 의존성 주입이 필요한 모든 곳에 공유되어 주입되며, 이는 대부분의 상태를 가지지 않는 서비스 객체나 공유 자원에 적합한 방식이다. 싱글톤 패턴을 직접 구현할 때 발생할 수 있는 복잡성을 스프링 프레임워크가 대신 관리해 준다.
프로토타입 스코프는 싱글톤과 대비되는 개념으로, 컨테이너에 빈을 요청할 때마다 새로운 인스턴스가 생성된다. 의존성 주입을 통해 이 빈을 참조하거나 애플리케이션 컨텍스트의 getBean() 메서드를 호출할 때마다 새로운 객체가 만들어져 반환된다. 이 스코프는 사용할 때마다 새로운 상태를 가져야 하는 객체나, 생명주기를 클라이언트 코드가 관리해야 하는 객체에 적합하다.
빈의 스코프는 XML 설정, 어노테이션, 또는 자바 설정을 통해 정의할 수 있다. XML에서는 <bean> 요소의 scope 속성을, 어노테이션 기반 설정에서는 @Scope 어노테이션을 사용하여 명시한다. 예를 들어, @Scope("prototype") 또는 @Scope("singleton")과 같이 지정한다. 스코프를 명시하지 않으면 기본값인 싱글톤이 적용된다.
싱글톤 빈이 프로토타입 빈을 의존성으로 가지고 있을 때는 주의가 필요하다. 싱글톤 빈은 컨테이너 생성 시점에 한 번만 의존성이 주입되므로, 싱글톤 빈 내부에 주입된 프로토타입 빈 인스턴스도 처음 주입된 하나로 고정되어 버린다. 이 경우 매번 새로운 프로토타입 인스턴스를 얻기 위해서는 추가적인 방법이 필요하다.
4.2. 빈 생명주기 콜백
4.2. 빈 생명주기 콜백
빈 생명주기 콜백은 스프링 프레임워크의 스프링 컨테이너가 빈 객체의 생성과 소멸 과정에서 특정 시점에 개발자가 정의한 로직을 실행할 수 있도록 하는 메커니즘이다. 이를 통해 빈이 초기화되기 직후나 컨테이너가 종료되어 빈이 소멸되기 직전에 필요한 자원을 할당하거나 해제하는 등의 작업을 수행할 수 있다.
주요 콜백 방법으로는 초기화 콜백과 소멸 콜백이 있다. 초기화 콜백은 빈의 모든 의존성 주입이 완료된 후 실행되어, 데이터베이스 연결 설정이나 캐시 준비와 같은 초기화 작업을 담당한다. 반대로 소멸 콜백은 빈이 컨테이너에서 제거되기 직전에 호출되어, 네트워크 연결 종료나 파일 핸들 정리와 같은 정리 작업을 수행한다.
이러한 콜백을 구현하는 방법은 여러 가지가 있다. 가장 일반적인 방법은 @PostConstruct와 @PreDestroy 어노테이션을 사용하는 것이다. 또한, InitializingBean과 DisposableBean 인터페이스를 구현하거나, 빈 설정 메타데이터에서 init-method 및 destroy-method 속성을 지정하는 방식도 사용할 수 있다. 각 방법은 사용 편의성과 코드 결합도 측면에서 장단점이 있다.
빈 생명주기 콜백을 올바르게 활용하면 애플리케이션의 자원 관리와 상태 일관성을 보다 견고하게 유지할 수 있다. 특히 외부 리소스를 사용하는 빈이나 시작 시 한 번만 설정해야 하는 구성 요소를 다룰 때 필수적인 기능으로 평가된다.
5. 의존성 주입 패턴
5. 의존성 주입 패턴
5.1. 생성자 주입
5.1. 생성자 주입
생성자 주입은 의존성 주입을 구현하는 주요 패턴 중 하나로, 빈이 필요로 하는 의존성을 생성자 매개변수를 통해 전달받는 방식이다. 이 방식은 객체가 생성되는 시점에 모든 필요한 의존 객체가 확보되어야 하므로, 빈의 불변성을 보장하고 완전히 초기화된 상태의 객체를 제공할 수 있다는 장점이 있다. 스프링 프레임워크에서는 생성자 주입을 권장하는 방식으로 간주하며, 특히 필수적인 의존 관계를 명시적으로 표현할 때 유용하다.
생성자 주입은 XML 기반 설정에서는 <constructor-arg> 요소를 사용하여 정의할 수 있으며, 어노테이션 기반 설정에서는 생성자에 @Autowired 어노테이션을 적용하여 구현한다. 자바 기반 설정에서는 @Bean 메서드 내에서 필요한 의존성을 생성자에 직접 전달하는 코드를 작성한다. 여러 개의 의존성이 있을 경우 생성자의 매개변수 순서나 타입, @Qualifier 어노테이션 등을 통해 어떤 빈을 주입할지 명시할 수 있다.
생성자 주입의 주요 이점은 의존성이 final 필드로 선언될 수 있어 런타임 중에 변경되지 않음을 보장할 수 있다는 점이다. 이는 스레드 안전성을 높이고, 코드의 의도를 더 명확하게 만든다. 또한, 단위 테스트를 작성할 때 모의 객체를 생성자에 쉽게 전달할 수 있어 테스트 용이성이 향상된다. 스프링 4.3 버전부터는 단일 생성자 있는 경우 @Autowired 어노테이션을 생략할 수 있어 코드가 더 간결해졌다.
생성자 주입은 순환 의존성이 발생하는 경우 문제를 초기 단계에서 발견할 수 있게 한다. 세터 주입이나 필드 주입과 달리 객체 생성 시점에 모든 의존성을 요구하기 때문에, 설계상의 결함을 컴파일 타임이나 애플리케이션 컨텍스트 초기화 단계에서 확인할 수 있다. 따라서 애플리케이션 컨텍스트의 설정 오류를 사전에 방지하고 보다 견고한 애플리케이션 구조를 만드는 데 기여한다.
5.2. 세터 주입
5.2. 세터 주입
세터 주입은 스프링 프레임워크가 제공하는 의존성 주입 패턴 중 하나로, 대상 빈의 세터 메서드를 통해 의존성을 주입하는 방식이다. 클래스에 의존 객체를 설정하기 위한 세터 메서드를 정의하고, 스프링 컨테이너가 XML 기반 설정이나 어노테이션 기반 설정을 통해 해당 메서드를 호출하여 필요한 의존 객체를 전달한다.
이 방식은 생성자 주입과 달리 객체 생성 후에 의존성을 설정할 수 있어, 선택적 의존성이나 변경 가능한 의존성을 다룰 때 유용하다. 또한 자바빈즈 규약을 따르는 일반적인 세터 메서드 형태를 사용하기 때문에 코드의 가독성과 일관성을 유지하는 데 도움이 된다. 스프링 3.1 이전에는 주로 <property> 태그를 사용한 XML 설정으로 구현되었으나, 현재는 @Autowired 어노테이션을 세터 메서드 위에 선언하는 방식이 더 일반적으로 사용된다.
그러나 세터 주입은 객체가 완전히 초기화된 상태(의존성이 주입된 상태)로 생성된다는 보장이 없으며, 주입이 필요한 필드가 final로 선언될 수 없다는 단점이 있다. 이로 인해 불변 객체를 만들기 어렵고, 런타임 중에 의존성이 변경될 가능성이 있어 생성자 주입에 비해 불변성을 보장하지 못한다. 따라서 필수적인 의존성에는 생성자 주입을, 선택적이거나 변경 가능성이 있는 의존성에는 세터 주입을 사용하는 것이 권장되는 방식이다.
주입 방식 | 메서드 | 변경 가능성 |
| 권장 사용처 |
|---|---|---|---|---|
세터 주입 | 세터 메서드 | 가능 | 불가능 | 선택적/변경 가능 의존성 |
생성자 주입 | 생성자 | 불가능 | 가능 | 필수 의존성 |
5.3. 필드 주입
5.3. 필드 주입
필드 주입은 스프링 프레임워크가 제공하는 의존성 주입 패턴 중 하나로, 빈의 필드에 직접 의존성을 주입하는 방식을 말한다. 이 방식은 @Autowired 어노테이션을 필드 선언부에 직접 적용하여 사용한다. 스프링 컨테이너는 애플리케이션 컨텍스트를 초기화하는 과정에서 해당 필드를 찾아 필요한 의존성을 자동으로 연결해준다.
이 방식의 가장 큰 장점은 코드가 매우 간결해진다는 점이다. 생성자나 세터 메서드를 별도로 작성할 필요 없이 필드 선언과 함께 어노테이션만 추가하면 되므로, 특히 소규모 프로젝트나 프로토타입 개발에서 빠르게 적용할 수 있다. 또한, 리팩토링 과정에서 의존성이 추가될 때 기존 생성자나 세터의 시그니처를 변경하지 않아도 된다는 편리함이 있다.
그러나 필드 주입은 몇 가지 심각한 단점을 가지고 있어 현대적인 스프링 개발에서는 권장되지 않는 방식이다. 첫째, 의존성을 필드에 직접 주입하기 때문에 해당 빈이 불변(Immutable) 상태를 보장할 수 없다. 둘째, 단위 테스트를 작성할 때 스프링 컨테이너 없이는 의존성을 주입할 방법이 없어 테스트가 어려워진다. 셋째, 필드 주입은 의존성을 숨기기 때문에 클래스가 어떤 의존성을 필요로 하는지 명시적으로 파악하기 어렵다.
이러한 문제들로 인해, 스프링 팀과 많은 커뮤니티 가이드라인에서는 생성자 주입을 기본 방식으로 권장한다. 생성자 주입은 의존성을 명시적으로 요구하며, 불변성을 보장하고, 테스트 용이성을 높여준다. 필드 주입은 레거시 코드에서 주로 발견되거나, 프레임워크 자체의 특수한 컴포넌트(예: JPA의 @PersistenceContext)를 사용할 때와 같은 제한된 경우에만 고려된다.
6. SpEL (스프링 표현 언어)
6. SpEL (스프링 표현 언어)
스프링 표현 언어는 스프링 프레임워크에서 사용되는 강력한 표현 언어이다. 빈 정의나 XML 설정 파일, 어노테이션 등 다양한 곳에서 객체 그래프를 조회하고 조작하는 데 사용된다. 런타임에 객체에 대한 질의와 조작을 가능하게 하여 설정을 보다 동적이고 유연하게 만드는 것이 주요 목적이다.
SpEL은 빈 속성 주입 시 복잡한 표현식을 간결하게 작성할 수 있게 해준다. 예를 들어, 다른 빈의 속성을 참조하거나, 메서드를 호출하며, 산술, 관계, 논리 연산을 수행할 수 있다. 또한 리스트나 맵 컬렉션의 요소에 접근하고 필터링하는 기능도 제공한다. 이는 의존성 주입 값을 하드코딩하는 대신 런타임 환경이나 다른 객체의 상태에 기반해 동적으로 결정해야 할 때 매우 유용하다.
SpEL의 구문은 다른 인기 있는 표현 언어들과 유사하며, 주로 해시 기호와 중괄호(#{)로 표현식을 감싸서 사용한다. 표현식은 애플리케이션 컨텍스트 내에서 평가되며, 미리 정의된 변수나 사용자 정의 변수를 활용할 수 있다. 스프링 시큐리티나 스프링 데이터 같은 다른 스프링 프로젝트들에서도 조건부 접근 제어나 쿼리 정의를 위해 SpEL을 광범위하게 활용하고 있다.
7. 리소스 관리
7. 리소스 관리
스프링 코어의 리소스 관리는 클래스패스, 파일 시스템, URL 등 다양한 위치에 존재하는 리소스 파일을 일관된 방식으로 접근하고 로드할 수 있게 해주는 추상화 계층을 제공한다. 이를 통해 애플리케이션 코드는 리소스의 실제 위치(예: 절대 파일 경로, 웹상의 주소)에 구애받지 않고, 필요한 리소스를 선언적으로 로드할 수 있다. 이 추상화의 핵심은 Resource 인터페이스이며, ClassPathResource, FileSystemResource, UrlResource 등이 주요 구현체이다.
애플리케이션 컨텍스트는 이러한 리소스 로딩을 위한 ResourceLoader 인터페이스를 구현하고 있어, classpath:, file:, http: 등의 접두어를 사용한 리소스 위치 지정자를 통해 리소스를 쉽게 획득할 수 있게 한다. 예를 들어, classpath:config/app.properties라는 문자열로 클래스패스 내의 설정 파일을 참조할 수 있다. 이는 특히 테스트 환경과 운영 환경에서 리소스 경로가 달라지는 상황에서 설정의 유연성을 크게 높여준다.
또한, 스프링은 의존성 주입을 통해 빈에 리소스를 주입하는 기능을 지원한다. @Value 어노테이션을 사용하면 프로퍼티 파일의 값을 직접 주입받을 수 있으며, Resource 타입의 프로퍼티나 생성자 인자에 리소스 경로를 지정하면 해당 리소스 객체가 자동으로 주입된다. 이는 XML 기반 설정에서도 <property> 태그나 <constructor-arg> 태그를 이용해 동일하게 구성 가능하다.
8. 이벤트 처리
8. 이벤트 처리
스프링 프레임워크의 이벤트 처리 기능은 애플리케이션 컨텍스트 내에서 발생하는 다양한 사건을 객체 지향적인 방식으로 발행하고 구독할 수 있게 해주는 모델을 제공한다. 이 모델은 컴포넌트 간의 느슨한 결합을 유지하면서 비즈니스 로직에 따른 커스텀 이벤트를 정의하고 처리하는 데 유용하다.
이벤트 처리의 핵심은 ApplicationEvent 클래스와 ApplicationListener 인터페이스, 그리고 이벤트를 발행하는 ApplicationEventPublisher 인터페이스이다. 개발자는 ApplicationEvent를 상속받아 자신만의 커스텀 이벤트 클래스를 정의할 수 있다. 예를 들어, 사용자 가입이 완료되었음을 알리는 UserRegisteredEvent나 주문이 생성되었음을 알리는 OrderCreatedEvent 등을 만들 수 있다. 이벤트를 수신하려는 컴포넌트는 ApplicationListener 인터페이스를 구현하거나 @EventListener 어노테이션을 메서드에 부여하여 구독자로 등록한다.
이벤트 발행은 주로 ApplicationEventPublisher의 publishEvent() 메서드를 통해 이루어진다. 스프링 빈은 ApplicationEventPublisherAware 인터페이스를 구현하거나 @Autowired를 통해 ApplicationEventPublisher를 주입받아 이벤트를 발행할 수 있다. 발행된 이벤트는 애플리케이션 컨텍스트에 의해 동기적으로 모든 구독자에게 전달되어 처리된다. 이는 트랜잭션 경계와 같은 컨텍스트 내에서 안전하게 이벤트를 처리할 수 있게 하지만, 장시간 실행되는 리스너는 전체 처리 흐름을 지연시킬 수 있다는 점에 유의해야 한다.
이 모델은 관점 지향 프로그래밍이나 직접적인 메서드 호출보다 더 유연한 방식으로 비즈니스 로직의 결과를 다른 모듈에 알리는 데 적합하다. 예를 들어, 주문 서비스가 주문을 저장한 후 OrderCreatedEvent를 발행하면, 이메일 발송 서비스와 포인트 적립 서비스가 각자의 리스너를 통해 이 이벤트를 독립적으로 처리할 수 있다. 이를 통해 서비스 간의 의존성을 제거하고 시스템의 확장성과 유지보수성을 높일 수 있다.
9. AOP (관점 지향 프로그래밍) 기초
9. AOP (관점 지향 프로그래밍) 기초
스프링 코어는 관점 지향 프로그래밍(AOP)을 지원하여 애플리케이션의 핵심 비즈니스 로직과 공통적으로 적용되는 부가 기능(예: 로깅, 트랜잭션 관리, 보안)을 분리할 수 있게 한다. 이를 통해 코드의 모듈성과 유지보수성을 크게 향상시킨다. AOP의 기본 개념은 핵심 관심사와 횡단 관심사를 분리하는 것이다. 횡단 관심사는 여러 클래스나 메서드에 걸쳐 반복적으로 나타나는 기능을 의미한다.
스프링 AOP는 프록시 기반의 방식으로 구현된다. 빈에 대한 프록시 객체를 생성하고, 지정된 조인 포인트에서 어드바이스라는 추가 동작을 끼워 넣는 방식으로 작동한다. 주요 구성 요소로는 어드바이스, 포인트컷, 애스펙트가 있다. 어드바이스는 '무엇을 할 것인가'에 해당하는 실제 부가 기능을 정의하며, 포인트컷은 '어디에 적용할 것인가'를 정의하여 특정 조인 포인트를 선별한다. 애스펙트는 이 어드바이스와 포인트컷을 하나로 묶은 모듈이다.
스프링 AOP에서 주로 사용되는 어드바이스의 유형에는 메서드 실행 전에 동작하는 @Before, 메서드 실행 후에 동작하는 @After, 성공적인 반환 후에 동작하는 @AfterReturning, 예외 발생 후에 동작하는 @AfterThrowing, 메서드 호출 전후에 동작하는 @Around 등이 있다. 이러한 어노테이션을 이용해 선언적으로 AOP를 적용할 수 있어 설정이 간편하다.
스프링의 AOP 지원은 스프링 트랜잭션 관리 같은 핵심 기능의 기반이 된다. 엔터프라이즈 애플리케이션에서 트랜잭션 경계를 비즈니스 메서드에 선언적으로 설정할 수 있게 해주는 것이 대표적인 사례이다. 이는 EJB와 같은 무거운 기술 없이도 POJO 기반으로 강력한 관점 지향 기능을 사용할 수 있게 한다.
10. 테스트 지원
10. 테스트 지원
스프링 코어는 애플리케이션의 견고한 테스트를 지원하기 위한 다양한 기능을 제공한다. 이는 단위 테스트와 통합 테스트를 모두 효과적으로 수행할 수 있도록 설계되어, 개발자가 소프트웨어 품질을 높이고 리팩토링을 안전하게 진행하는 데 기여한다. 스프링의 의존성 주입 패턴은 테스트 시에 가짜 객체인 목 객체나 스텁을 쉽게 주입할 수 있게 하여, 특정 컴포넌트를 고립시켜 테스트하는 것을 용이하게 한다.
스프링은 spring-test 모듈을 통해 전용 테스트 지원 프레임워크를 제공한다. 이 모듈의 핵심은 @SpringBootTest, @WebMvcTest, @DataJpaTest와 같은 어노테이션을 사용하여 테스트에 필요한 스프링 애플리케이션 컨텍스트를 쉽게 로드하고 구성할 수 있게 하는 것이다. 또한 MockMvc는 웹 계층을 실제 서버를 구동하지 않고도 테스트할 수 있는 강력한 도구이다.
테스트 실행 시 애플리케이션 컨텍스트의 캐싱 메커니즘은 성능을 최적화한다. 동일한 구성으로 정의된 테스트들은 컨텍스트를 재사용하여 불필요한 재로딩 시간을 줄여준다. TestContext 프레임워크는 테스트 전후에 데이터베이스 트랜잭션을 관리하거나 테스트용 빈을 정의하는 기능도 지원하여, 깨끗한 테스트 환경을 유지하는 데 도움을 준다.
이러한 테스트 지원은 JUnit 및 TestNG와 같은 널리 사용되는 테스트 프레임워크와 자연스럽게 통합되어 작동한다. 개발자는 익숙한 테스트 작성 방식을 유지하면서도 스프링 컨테이너의 강력한 기능을 활용하여 복잡한 의존성을 가진 비즈니스 로직이나 데이터 접근 계층을 효과적으로 검증할 수 있다.
